{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "6a9f5f75",
   "metadata": {
    "colab_type": "text",
    "id": "view-in-github"
   },
   "source": [
    "<a href=\"https://colab.research.google.com/github/peremartra/Large-Language-Model-Notebooks-Course/blob/main/3-LangChain/LangChain_OpenAI_Moderation_System.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "NmKV7nA_w7Y3",
   "metadata": {
    "id": "NmKV7nA_w7Y3"
   },
   "source": [
    "<div align=\"center\">\n",
    "<h1><a href=\"https://github.com/peremartra/Large-Language-Model-Notebooks-Course\">LLM Hands On Course</a></h1>\n",
    "    <h3>Understand And Apply Large Language Models</h3>\n",
    "    <h2>Create a Moderation system with LangChain and OpenAI.</h2>\n",
    "    <p>by <b>Pere Martra</b></p>\n",
    "</div>\n",
    "\n",
    "<br>\n",
    "\n",
    "<div align=\"center\">\n",
    "    &nbsp;\n",
    "    <a target=\"_blank\" href=\"https://www.linkedin.com/in/pere-martra/\"><img src=\"https://img.shields.io/badge/style--5eba00.svg?label=LinkedIn&logo=linkedin&style=social\"></a>\n",
    "    \n",
    "</div>\n",
    "\n",
    "<br>\n",
    "<hr>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ou6VyCbgzAnu",
   "metadata": {
    "id": "ou6VyCbgzAnu"
   },
   "source": [
    "# How To Create a Moderation System Using LangChain.\n",
    "\n",
    "We are goin to create a Moderation System based in two Models. The first Model  reads the User comments and answer them.\n",
    "\n",
    "The second language Model receives the answer of the first model and identify any kind on negativity modifiyng if necessary the comment.\n",
    "\n",
    "With the intention of preventing a text entry by the user from influencing a negative or out-of-tone response from the comment system."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b64d83f8",
   "metadata": {
    "id": "b64d83f8"
   },
   "outputs": [],
   "source": [
    "#Install de LangChain and openai libraries.\n",
    "!pip install -q langchain==0.1.4\n",
    "!pip install -q langchain-openai==0.0.5\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f7BJehSLKY3l",
   "metadata": {
    "id": "f7BJehSLKY3l"
   },
   "source": [
    "## Importing LangChain Libraries.\n",
    "* PrompTemplate: provides functionality to create prompts with parameters.\n",
    "* OpenAI:  To interact with the OpenAI models.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c46865d3",
   "metadata": {
    "id": "c46865d3"
   },
   "outputs": [],
   "source": [
    "#PrompTemplate is a custom class that provides funcrionality to create prompts\n",
    "from langchain import PromptTemplate\n",
    "from langchain.chat_models import ChatOpenAI\n",
    "from langchain_core.output_parsers import StrOutputParser\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9b6f596d",
   "metadata": {
    "id": "9b6f596d"
   },
   "outputs": [],
   "source": [
    "import torch\n",
    "import os\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "CPBCSDNIK9A_",
   "metadata": {
    "id": "CPBCSDNIK9A_"
   },
   "source": [
    "We need an OpenAI key to interac with the OpenAI API.\n",
    "\n",
    "Here you can acces to your keys.\n",
    "https://platform.openai.com/account/api-keys\n",
    "\n",
    "OpenAI it's a pay service and you need a credit card to get a Key. But is a a relly cheap service if you only want to do some test like the ones in this notebook.\n",
    "\n",
    "I'm using the gpt-3.5 as a moderator.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "_aOzLspNK6oF",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "_aOzLspNK6oF",
    "outputId": "3fa64040-825f-4c3e-8ff2-f57e1be91e92"
   },
   "outputs": [],
   "source": [
    "from getpass import getpass\n",
    "os.environ[\"OPENAI_API_KEY\"] = getpass(\"OpenAI API Key: \")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "JtpT0hdNgpei",
   "metadata": {
    "id": "JtpT0hdNgpei"
   },
   "outputs": [],
   "source": [
    "assistant_llm = ChatOpenAI(model=\"gpt-3.5-turbo\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "WenwSqA8rCun",
   "metadata": {
    "id": "WenwSqA8rCun"
   },
   "source": [
    "Create the template for the first model called **assistant**.\n",
    "\n",
    "The prompt receives 2 variables, the sentiment and the customer_request, or customer comment.\n",
    "\n",
    "I included the sentiment to facilitate the creation of rude or incorrect answers."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1f807bde",
   "metadata": {
    "id": "1f807bde"
   },
   "outputs": [],
   "source": [
    "# Instruction how the LLM must respond the comments,\n",
    "assistant_template = \"\"\"\n",
    "You are {sentiment} assistant that responds to user comments,\n",
    "using similar vocabulary than the user.\n",
    "User:\" {customer_request}\"\n",
    "Comment:\n",
    "\"\"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "da37640c",
   "metadata": {
    "id": "da37640c"
   },
   "outputs": [],
   "source": [
    "#Create the prompt template to use in the Chain for the first Model.\n",
    "assistant_prompt_template = PromptTemplate(\n",
    "    input_variables=[\"sentiment\", \"customer_request\"],\n",
    "    template=assistant_template\n",
    ")\n",
    "\n",
    "output_parser = StrOutputParser()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "DqfeK6Cvy6u1",
   "metadata": {
    "id": "DqfeK6Cvy6u1"
   },
   "source": [
    "Now we create a First Chain. Just chaining the assistant_prompt_template and the model. The model will receive the prompt generated with the prompt_template.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9913a830",
   "metadata": {
    "id": "9913a830"
   },
   "outputs": [],
   "source": [
    "#assistant_chain = LLMChain(\n",
    "#    llm=assistant_llm,\n",
    "#    prompt=assistant_prompt_template,\n",
    "#    output_key=\"assistant_response\",\n",
    "#    verbose=False,\n",
    "#)\n",
    "\n",
    "assistant_chain = assistant_prompt_template | assistant_llm | output_parser\n",
    "\n",
    "#the output of the formatted prompt will pass directly to the LLM."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "Ed_1oKYDUKnT",
   "metadata": {
    "id": "Ed_1oKYDUKnT"
   },
   "source": [
    "To execute the chain created it's necessary to call the **.run** method of the chain, and pass the variables necessaries.\n",
    "\n",
    "In our case: *customer_request* and *sentiment*."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c1fd579b-c712-49b9-a125-98d515fc4ec2",
   "metadata": {
    "id": "c1fd579b-c712-49b9-a125-98d515fc4ec2"
   },
   "outputs": [],
   "source": [
    "#Support function to obtain a response to a user comment.\n",
    "def create_dialog(customer_request, sentiment):\n",
    "    #calling the .invoke method from the chain created Above.\n",
    "    assistant_response = assistant_chain.invoke(\n",
    "        {\"customer_request\": customer_request,\n",
    "        \"sentiment\": sentiment}\n",
    "    )\n",
    "    return assistant_response"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "qm3hKo1vUq3-",
   "metadata": {
    "id": "qm3hKo1vUq3-"
   },
   "source": [
    "## Obtain answers from our first Model Unmoderated.\n",
    "\n",
    "The customer post is really rude, we are looking for a rude answer from our Model, and to obtain it we are changing the sentiment."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1c149402",
   "metadata": {
    "id": "1c149402"
   },
   "outputs": [],
   "source": [
    "# This is the customer request, or customer comment in the forum moderated by the agent.\n",
    "# feel free to modify it.\n",
    "customer_request = \"\"\"This product is a piece of shit. I feel like an Idiot!\"\"\"\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3da106c6-79ab-4fd5-9e67-3c5ab31de541",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "3da106c6-79ab-4fd5-9e67-3c5ab31de541",
    "outputId": "811564af-5e14-4272-b7e5-0c71a09093c4"
   },
   "outputs": [],
   "source": [
    "# Our assistant working in 'nice' mode.\n",
    "response_data=create_dialog(customer_request, \"nice\")\n",
    "print(f\"assistant response: {response_data}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ewZx6k3QVP-k",
   "metadata": {
    "id": "ewZx6k3QVP-k"
   },
   "source": [
    "The answer obtained is really polite. It dosn't need moderation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1cdf1452",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "1cdf1452",
    "outputId": "d80633e9-992a-41c4-c8eb-c8576a267596"
   },
   "outputs": [],
   "source": [
    "#Our assistant running in rude mode.\n",
    "response_data = create_dialog(customer_request, \"most rude possible \")\n",
    "print(f\"assistant response: {response_data}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ro5bPqPk1O3y",
   "metadata": {
    "id": "ro5bPqPk1O3y"
   },
   "source": [
    "As you can see the answers we obtain are not polite and we can't publish this messages to the forum, especially if they come from our company's AI assistant."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "747b86ca",
   "metadata": {
    "id": "747b86ca"
   },
   "source": [
    "## Moderator\n",
    "Let's create the second moderator. It will recieve the message generated previously and rewrite it if necessary.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2dee251c",
   "metadata": {
    "id": "2dee251c"
   },
   "outputs": [],
   "source": [
    "#The moderator prompt template\n",
    "moderator_template = \"\"\"\n",
    "You are the moderator of an online forum, you are strict and will not tolerate any negative comments.\n",
    "You will receive a Original comment and if it is impolite you must transform in polite.\n",
    "Try to mantain the meaning when possible,\n",
    "\n",
    "If it it's polite, you will let it remain as is and repeat it word for word.\n",
    "Original comment: {comment_to_moderate}\n",
    "\"\"\"\n",
    "# We use the PromptTemplate class to create an instance of our template that will use the prompt from above and store variables we will need to input when we make the prompt.\n",
    "moderator_prompt_template = PromptTemplate(\n",
    "    input_variables=[\"comment_to_moderate\"],\n",
    "    template=moderator_template,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "JbDldR8P7Dc4",
   "metadata": {
    "id": "JbDldR8P7Dc4"
   },
   "outputs": [],
   "source": [
    "#I'm going to use a more advanced LLM\n",
    "moderator_llm = ChatOpenAI(model=\"gpt-4\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "aYFefTo17COd",
   "metadata": {
    "id": "aYFefTo17COd"
   },
   "outputs": [],
   "source": [
    "#We build the chain for the moderator.\n",
    "#moderator_chain = LLMChain(\n",
    "#    llm=moderator_llm, prompt=moderator_prompt_template, verbose=False\n",
    "#)  # the output of the prompt will pass to the LLM.\n",
    "\n",
    "moderator_chain = moderator_prompt_template | moderator_llm | output_parser"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5311c7f4",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "5311c7f4",
    "outputId": "07e3866b-6207-45cd-be44-c8f8a211b6c6"
   },
   "outputs": [],
   "source": [
    "# To run our chain we use the .run() command\n",
    "moderator_data = moderator_chain.invoke({\"comment_to_moderate\": response_data})\n",
    "print(moderator_data)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "HfXgsYQk7VYC",
   "metadata": {
    "id": "HfXgsYQk7VYC"
   },
   "source": [
    "Maybe the message is not perfect, but for sure that is more polite than the one produced by the ***rude assistant***."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "27074b92",
   "metadata": {
    "id": "27074b92"
   },
   "source": [
    "## LangChain System\n",
    "Now is Time to put both models in the same Chain and that they act as if they were a sigle model.\n",
    "\n",
    "We have both models, amb prompt templates, we only need to create a new chain and see hot it works.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "eWA4Knv43Sw4",
   "metadata": {
    "id": "eWA4Knv43Sw4"
   },
   "outputs": [],
   "source": [
    "assistant_moderated_chain = (\n",
    "    {\"comment_to_moderate\":assistant_chain}\n",
    "    |moderator_chain\n",
    ")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "DJvcPrnu9hgV",
   "metadata": {
    "id": "DJvcPrnu9hgV"
   },
   "source": [
    "**SequentialChain** is used to link different chains and parameters.\n",
    "\n",
    "It's necessary to indicate the chains and the parameters that we shoud pass in the **.invoke** method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b368421a",
   "metadata": {
    "id": "b368421a"
   },
   "outputs": [],
   "source": [
    "#from langchain.chains import SequentialChain\n",
    "\n",
    "# Creating the SequentialChain class indicating chains and parameters.\n",
    "#assistant_moderated_chain = SequentialChain(\n",
    "#    chains=[assistant_chain, moderator_chain],\n",
    "#    input_variables=[\"sentiment\", \"customer_request\"],\n",
    "#    verbose=True\n",
    "#)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "pwEJwz2GRGrt",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 1000
    },
    "id": "pwEJwz2GRGrt",
    "outputId": "fbd3cc73-8fbb-4ebd-fb60-0703b341e156"
   },
   "outputs": [],
   "source": [
    "# We can now run the chain.\n",
    "from langchain.callbacks.tracers import ConsoleCallbackHandler\n",
    "assistant_moderated_chain.invoke({\"sentiment\": \"nice\", \"customer_request\": customer_request},\n",
    "                                 config={'callbacks':[ConsoleCallbackHandler()]})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "36VAKQWjRGV4",
   "metadata": {
    "id": "36VAKQWjRGV4"
   },
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "T_GESASs-OVo",
   "metadata": {
    "id": "T_GESASs-OVo"
   },
   "source": [
    "Lets use our Moderating System!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "61085f57",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 1000
    },
    "id": "61085f57",
    "outputId": "3980960a-ffb9-4981-d168-ff6bbff8ae63"
   },
   "outputs": [],
   "source": [
    "# We can now run the chain.\n",
    "assistant_moderated_chain.invoke({\"sentiment\": \"impolite\", \"customer_request\": customer_request},\n",
    "                                 config={'callbacks':[ConsoleCallbackHandler()]})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "mBJ-J3wN-hNh",
   "metadata": {
    "id": "mBJ-J3wN-hNh"
   },
   "source": [
    "Every time you execute this function you can get different messages, but for sure than the one in the ***Finished Chain*** generated by our ***moderator*** is more suitable than the one in Original Comment generated by our ***rude assistant***."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3bf75f01",
   "metadata": {
    "id": "3bf75f01"
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "colab": {
   "include_colab_link": true,
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
